#pragma once

#include <string>
#include <string_view>


class CharInputStream
{
public:
    virtual ~CharInputStream() {}
    virtual bool hasNext() = 0;
    virtual char next() = 0;
};


class StrViewCharInputStream : public CharInputStream
{
public:
    StrViewCharInputStream(const std::string_view &view)
        : view_(view) {}

    virtual ~StrViewCharInputStream() {}

    virtual bool hasNext() override {
        return pos_ < view_.size();
    }

    virtual char next() override {
        return view_[pos_++];
    }

private:
    const std::string_view view_;
    std::string_view::size_type pos_ = 0;
};


class RestorableCharInputStream : public CharInputStream
{
public:
    RestorableCharInputStream(CharInputStream &in)
        : in_(in) {}

    virtual ~RestorableCharInputStream() {}

    virtual bool hasNext() override {
        if (!buff_.empty()) {
            return true;
        }
        return in_.hasNext();
    }

    virtual char next() override {
        if (!buff_.empty()) {
            const char c = buff_.back();
            buff_.pop_back();
            return c;
        }
        return in_.next();
    }

    void restore(char c) {
        buff_.push_back(c);
    }

    void restore(const std::string &str) {
        using Size = decltype(str.size());
        const Size len = str.size();
        for (Size i = len - 1; i < len && i >= 0; --i) {
            restore(str[i]);
        }
    }

    void restore(const std::string_view &str) {
        using Size = decltype(str.size());
        const Size len = str.size();
        for (Size i = len - 1; i < len && i >= 0; --i) {
            restore(str[i]);
        }
    }

private:
    std::string buff_;
    CharInputStream &in_;
};


class UnescapeCharInputStream : public CharInputStream
{
public:
    UnescapeCharInputStream(CharInputStream &in)
        : in_(in) {}

    virtual ~UnescapeCharInputStream() {}

    virtual bool hasNext() override;

    virtual char next() override;

private:

    enum class State
    {
        NoNextChar,
        NextCharNotLoaded,
        NextCharLoaded,
    };

    void tryLoadNextChar();

private:
    State st_ = State::NextCharNotLoaded;
    char nextChar_ = 0;
    CharInputStream &in_;
};

